#include "x_mysql.h"
#include "../../NetCore/xLog/xLog.h"
#include "../../NetCore/xDef.h"
#include <stdarg.h>
namespace x_3rd
{
    x_mysql::~x_mysql()
    {
        if(mysql)
        {
            mysql_close(mysql);
            mysql = NULL;
            mysql_library_end();
        }
        isconnected = false;
    }
    bool x_mysql::init()
    {
        mysql_library_init(-1, NULL, NULL);
        isinit = true;
        mysql = mysql_init(NULL);
        if(mysql == NULL)
        {
            XLOG_ERROR("mysql_init return fail");
            return false;
        }
        return true;
    }
    bool x_mysql::init(const char* ip,const char* name,const char* passw,uint32_t port,\
            const char* db_name,const char* charset)
    {
        db_server_port = port;
        db_server_ip = ip;
        db_name = name;
        db_user_name = db_name;
        db_user_passw = passw;
        db_charset = charset;
        return init();
    }

    bool x_mysql::connect()
    {
        if(!isinit && isconnected && mysql != NULL)
        {
            return false;
        }
        int timeout = 3;
        //set timeout connect op
        mysql_options(mysql,mysql_option::MYSQL_OPT_CONNECT_TIMEOUT,&timeout);
        //set database charset
        if(mysql_set_character_set(mysql, "utf8") != 0)
        {
            XLOG_ERROR("mysql_set_character_set fail");
            return false;
        }
        //自动重连
        int recon = 1;
        int re = mysql_options(mysql, MYSQL_OPT_RECONNECT, &recon);
        if (re != 0)
        {
            XLOG_ERROR("mysql_options failed! %s",mysql_error(mysql));
            return false;
        }
        mysql = mysql_real_connect(mysql,db_server_ip.c_str(),db_user_name.c_str(),\
        db_user_passw.c_str(),db_name.c_str(),db_server_port,NULL,0);
        if(!mysql)
        {
            XLOG_ERROR("mysql_real_connect fail");
            return false;
        }
        isconnected = true;
        XLOG_INFO("mysql connect success");
        return true;
    }
    void x_mysql::disconnect()
    {
        if(mysql)
        {
            mysql_close(mysql);
            mysql = NULL;
        }
        isconnected = false;
    }

    bool x_mysql::execute(const char* exec_sql)
    {
        if(mysql == NULL)
        {
            if(!isinit || !connect())
            {
                XLOG_ERROR("execute sql,not init or not connect");
                return false;
            }
        }

        last_sql = exec_sql;
        int ret = mysql_real_query(mysql,exec_sql,strlen(exec_sql));
        if(ret != 0)
        {
            auto error_code = mysql_errno(mysql);
            XLOG_ERROR("mysql_real_query execute fail error:%u",error_code);
            if(error_code == 2006 || error_code == 2013)
            {
                if(mysql && connect())
                {
                    ret = mysql_real_query(mysql,exec_sql,strlen(exec_sql));
                }
            }
        }
        if(ret != 0)
        {
            XLOG_ERROR("execute sql fail,mysql timeout disconnect , error sql:%s",exec_sql);
            return false;
        }
        return true;
    }
    size_t x_mysql::sql_insert(const std::string& table_name,const DataRecord& record)
    {
        std::string sql = x_insert_sql_builder::build(mysql,table_name,record);
        if(sql.empty())
        {
            XLOG_ERROR("sql insert fail");
            return 0;
        }
        execute(sql.c_str());
        return mysql_affected_rows(mysql);
    }
    size_t x_mysql::sql_update(const std::string& table_name,const DataRecord& record,\
    const DataRecord& wheres,Conditions& condis)
    {
        std::string sql = x_update_sql_builder::build(mysql,table_name,record,wheres,condis);
        if(sql.empty())
        {
            XLOG_ERROR("update sql fail");
            return 0;
        }
        execute(sql.c_str());
        return mysql_affected_rows(mysql);
    }
    size_t x_mysql::sql_delete(const std::string& table_name,const DataRecord& record,Conditions& condis)
    {
        std::string sql = x_delete_sql_builder::build(mysql,table_name,record,condis);
        if(sql.empty())
        {
            XLOG_ERROR("delete sql fail");
            return 0;
        }
        execute(sql.c_str());
        return mysql_affected_rows(mysql);
    }
    size_t x_mysql::execute_raw_sql(const std::string& sql)
    {
        if(!execute(sql.c_str()))
        {
            return 0;
        }
        return mysql_affected_rows(mysql);
    }
    size_t x_mysql::execute_raw_sql(const char* format ...)
    {
        if(!format) return 0;
        va_list ap; 
        std::string szQuery;
        szQuery.reserve(MAX_QUERY_LEN);
        va_start(ap, format);  
        int res = vsnprintf( (char *)szQuery.c_str(), MAX_QUERY_LEN, format, ap );
        va_end(ap);  
        if(res == -1) 
        {  
            XLOG_ERROR("execute_raw_sql fail");
            return 0;
        }  
        //XLOG_DEBUG("execute sql:%s",szQuery.c_str());
        if(!execute(szQuery.c_str()))
        {
            return 0;
        }
        return mysql_affected_rows(mysql);
    }
    DataResult x_mysql::sql_query(const char* format ...)
    {
        DataResult empty_data;
        if(!format) return empty_data;
        va_list ap; 
        std::string szQuery;
        szQuery.reserve(MAX_QUERY_LEN);
        va_start(ap, format);  
        int res = vsnprintf( (char *)szQuery.c_str(), MAX_QUERY_LEN, format, ap );
        va_end(ap);  
        if(res == -1) 
        {  
            XLOG_ERROR("sql_query fail");
            return empty_data;
        }  
        return sql_query(szQuery);
    }
    DataResult x_mysql::sql_query(const std::string& sql)
    {
        DataResult data;
        if(!execute(sql.c_str()))
        {
            XLOG_ERROR("select sql fail,sql=%s",sql.c_str());
            return data;
        }
        MYSQL_RES* res = mysql_store_result(mysql);
        if(res == NULL)
        {
            XLOG_ERROR("mysql_store_result res is nullptr");
            return data;
        }
        std::vector<std::pair<std::string,Field::FIELD_DATA_TYPE> > fields;
        MYSQL_FIELD *field;
        while((field = mysql_fetch_field(res)))
        {
            fields.push_back({field->name,praseFieldType(field->type)});
        }
        DataRow data_row;
        MYSQL_ROW stRow;
        while((stRow = mysql_fetch_row(res)) != (MYSQL_ROW)NULL)
        {
            data_row.getDataRow().clear();
            unsigned long * lengths = mysql_fetch_lengths(res);
            for(size_t i = 0; i < fields.size(); i++)
            {
                Field insert_field;
                if(stRow[i])
                {
                    insert_field.setName(fields[i].first);
                    insert_field.setType(fields[i].second);
                    insert_field.setValue(std::string(stRow[i], lengths[i]));
                    insert_field.setValueStrLen(lengths[i]);
                    data_row.push(insert_field);
                }
                else
                {
                    data_row.push(insert_field);
                }
            }
            data.data().push_back(data_row);
        }
        mysql_free_result(res);
        return data;
    }
    DataResult x_mysql::sql_query(const std::string& table_name,const std::vector<std::string> &fields,\
    const DataRecord& record,Conditions& condis)
    {
        DataResult data;
        if(mysql == NULL)
        {
            if(!isinit || !connect())
            {
                XLOG_ERROR("mysql connect close");
                return data;
            }
        }
        std::string sql = x_query_sql_builder::build(mysql,table_name,fields,record,condis);
        return sql_query(sql);
    }
    bool x_mysql::sql_existRecord(const std::string& table_name,const std::vector<std::string> &fields,\
    const DataRecord& record,const Conditions& condis)
    {
        std::string sql = x_query_sql_builder::build(mysql,table_name,fields,record,condis);
        if(sql.empty()) return false;
        execute(sql.c_str());
        return mysql_affected_rows(mysql) > 0;
    }
    uint64_t x_mysql::last_insertID()
    {
        return mysql_insert_id(mysql);
    }
    uint64_t x_mysql::getAffectedRows()
    {
        return mysql_affected_rows(mysql);
    }
    void x_mysql::show_dataResult(DataResult& result)
    {
    }
    std::string x_mysql::escapeString(std::string& sFrom)
    {
        std::string::size_type iLen = sFrom.length() * 2 + 1;
        char *pTo = (char *)malloc(iLen);

        memset(pTo, 0x00, iLen);
        mysql_real_escape_string(mysql, pTo, sFrom.c_str(), sFrom.length());
        sFrom = pTo;
        free(pTo);
        return sFrom;
    }
    Field::FIELD_DATA_TYPE x_mysql::praseFieldType(enum_field_types type)
    {
        switch(type)
        {
            case FIELD_TYPE_BLOB:
                return Field::FIELD_DATA_TYPE::M_FIELD_TYPE_BLOB;
            case FIELD_TYPE_TIMESTAMP:
            case FIELD_TYPE_DATE:
            case FIELD_TYPE_TIME:
            case FIELD_TYPE_DATETIME:
            case FIELD_TYPE_YEAR:
            case FIELD_TYPE_STRING:
            case FIELD_TYPE_VAR_STRING:
            case FIELD_TYPE_SET:
            case FIELD_TYPE_NULL:
                return Field::FIELD_DATA_TYPE::M_FIELD_TYPE_STR;
            case FIELD_TYPE_TINY:

            case FIELD_TYPE_SHORT:
            case FIELD_TYPE_LONG:
            case FIELD_TYPE_INT24:
            case FIELD_TYPE_LONGLONG:
            case FIELD_TYPE_ENUM:
                return Field::FIELD_DATA_TYPE::M_FIELD_TYPE_INT;
            case FIELD_TYPE_DECIMAL:
            case FIELD_TYPE_FLOAT:
            case FIELD_TYPE_DOUBLE:
                return Field::FIELD_DATA_TYPE::M_FIELD_TYPE_FLOAT;
            default:
                return Field::FIELD_DATA_TYPE::M_FIELD_TYPE_UNKOWN;
        }
    }   
}